package com.g0dkar.leet.util.ognl.typeConversion;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.g0dkar.leet.core.Leet;
import com.g0dkar.leet.core.configuration.LeetConfiguration;
import com.g0dkar.leet.log.Log;
import com.g0dkar.leet.util.reflection.ReflectionUtils;

/**
 * Manages all kind of {@link Class Type} conversion from and to {@link String}.
 * 
 * @author Rafael g0dkar Lins
 * 
 */
public class TypeConverterManager {
	private static final List<TypeConverter> TYPE_CONVERTERS;
	
	static {
		TYPE_CONVERTERS = new ArrayList<TypeConverter>();
		init();
	}
	
	private static void init() {
		Log.debug("Buscando classes com @JHeatTypeConverter...");
		LeetConfiguration leetConfiguration = Leet.getInstance().getConfiguration();
		
		// JHeat Type Converters (they must be within this class' package)
		Log.debug("Buscando classes-default do JHeat no pacote: " + TypeConverterManager.class.getPackage().getName());
		Collection<Class<?>> foundConverters = new ArrayList<Class<?>>();
		
		foundConverters.addAll(ReflectionUtils.getAllClasses(TypeConverterManager.class.getPackage().getName(), null, LeetTypeConverter.class));
		
		// User Widgets
		if (leetConfiguration != null) {
			foundConverters.addAll(ReflectionUtils.getAllClasses(leetConfiguration.getBasePackage(), null, LeetTypeConverter.class));
		}
		
		if (foundConverters != null && !foundConverters.isEmpty()) {
			TYPE_CONVERTERS.clear();
			TypeConverter newInstance;
			
			for (Class<?> typeConverterClass : foundConverters) {
				if (TypeConverter.class.isAssignableFrom(typeConverterClass)) {
					newInstance = (TypeConverter) ReflectionUtils.newInstance(typeConverterClass);
					
					if (newInstance != null) {
						TYPE_CONVERTERS.add(newInstance);
					}
					else {
						Log.warn("Could not instantiate \"{}\" type converter. It's conversion features won't be available.", typeConverterClass.getName());
					}
				}
				else {
					Log.warn("Could not instantiate \"{}\" type converter. It do not implement the {} interface.", typeConverterClass.getName(), TypeConverter.class.getName());
				}
			}
		}
		else {
			Log.debug("Nenhuma classe encontrada.");
		}
	}
	
	/**
	 * Returns a {@link TypeConverter} for the given type, or <code>null</code> if there's no one.
	 * 
	 * @param type
	 *            The {@link Class} which'll be converted
	 * @return The {@link TypeConverter}.
	 */
	public static TypeConverter getConverterFor(Class<?> type) {
		for (TypeConverter converter : TYPE_CONVERTERS) {
			if (converter.canConvert(type)) {
				return converter;
			}
		}
		
		return null;
	}
	
	/**
	 * Faster check if a {@link TypeConverter} exists then to check if a call to
	 * <code>{@link #getConverterFor(Class)}</code> returns <code>null</code>.
	 * 
	 * @param type
	 *            The {@link Class} which'll be converted
	 * @return <code>true</code> if there's a {@link TypeConverter} for this type. <code>false</code> otherwise.
	 */
	public static boolean hasConverterFor(Class<?> type) {
		for (TypeConverter converter : TYPE_CONVERTERS) {
			if (converter.canConvert(type)) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * <p>
	 * Helper method that searches for a appropriate {@link TypeConverter} and call
	 * {@link TypeConverter#toString(Object, Class...)}.
	 * </p>
	 * 
	 * <p>
	 * {@link ReflectionUtils#isPrimitive(Class) Primitives} won't be converted with converters. This method'll use
	 * {@link String#valueOf(Object)} instead.
	 * </p>
	 * 
	 * @param value
	 *            The value to be converted.
	 * @param genericTypes
	 *            Any generic types information
	 * @return The value as a String or <code>null</code> if no converter could be found or a exception happened while
	 *         converting.
	 */
	public static String toString(Object value, Class<?>... genericTypes) {
		if (ReflectionUtils.isPrimitive(value.getClass())) {
			return String.valueOf(value);
		}
		
		TypeConverter typeConverter = getConverterFor(value.getClass());
		
		if (typeConverter != null) {
			try {
				return typeConverter.toString(value, genericTypes);
			} catch (Exception e) {
				return null;
			}
		}
		
		return null;
	}
	
	/**
	 * <p>
	 * Helper method that searches for a appropriate {@link TypeConverter} and call
	 * {@link TypeConverter#toDisplayableString(Object, Class...)}.
	 * </p>
	 * 
	 * @param value
	 *            The value to be converted.
	 * @param genericTypes
	 *            Any generic types information
	 * @return The value as a String or <code>{@link String#toString()}</code> if no converter could be found or a
	 *         exception happened while converting.
	 */
	public static String toDisplayableString(Object value, Class<?>... genericTypes) {
		TypeConverter typeConverter = getConverterFor(value.getClass());
		
		if (typeConverter != null) {
			try {
				return typeConverter.toDisplayableString(value, genericTypes);
			} catch (Exception e) {
				return null;
			}
		}
		
		return value.toString();
	}
	
	/**
	 * Helper method that searches for a appropriate {@link TypeConverter} and call
	 * {@link TypeConverter#toString(Object, Class...)}.
	 * 
	 * @param value
	 *            The value
	 * @param toType
	 *            The type
	 * @param genericTypes
	 *            The generic's types
	 * @return The Object or <code>null</code> if no converter could be found or a exception happened while
	 *         converting.
	 */
	public static Object fromString(String value, Class<?> toType, Class<?>... genericTypes) {
		if (toType.equals(String.class)) {
			return value;
		}
		
		TypeConverter typeConverter = getConverterFor(toType);
		
		if (typeConverter != null) {
			try {
				return typeConverter.fromString(value, toType, genericTypes);
			} catch (Exception e) {
				return null;
			}
		}
		
		return null;
	}
}
